package pl.edu.utp.gui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Observable;
import java.util.Random;

import de.congrace.exp4j.UnknownFunctionException;
import de.congrace.exp4j.UnparsableExpressionException;

import pl.edu.utp.calculations.IndividualComparator;
import pl.edu.utp.calculations.RangeCheck;
import pl.edu.utp.calculations.RateFunction;
import pl.edu.utp.calculations.Range;
import pl.edu.utp.ga.Individual;
import pl.edu.utp.ga.Parents;
import pl.edu.utp.ga.Population;
import pl.edu.utp.ga.genotypes.BinaryGenotype;
import pl.edu.utp.ga.interfaces.Genotype;
import pl.edu.utp.ga.interfaces.Selection;

public class Algorithm extends Observable{
	
	private int populationSize;
	private double mutationPropability;
	private double crossPropability;
	private double elitePercent;
	private String function;
	private String[] variables = {"x","y"};
	private ArrayList<Range> ranges;
	private int numberOfIterations = Integer.MAX_VALUE;
	private boolean stop = false;
	private Population firstPopulation;
	private Population bestPopulation;
	private Population lastPopulation;
	private Selection selection;
	private Class<? extends Genotype> genotypeClass = BinaryGenotype.class;
	
	private Random random;
	private RateFunction calculation;
	
	public Algorithm(int populationSize, double mutationPropability, double crossPropability, double elitePercent){
		this.populationSize = populationSize;
		this.mutationPropability = mutationPropability;
		this.crossPropability = crossPropability;
		this.elitePercent = elitePercent;
		random = new Random();
	}
	
	public Algorithm(int populationSize, double mutationPropability, double crossPropability, double elitePercent, String function) throws UnknownFunctionException, UnparsableExpressionException{
		this(populationSize,mutationPropability,crossPropability, elitePercent);
		this.function = function;
		createRateFunction();
	}
	
	public Algorithm(int populationSize, double mutationPropability, double crossPropability, double elitePercent, String function, String[] variables) throws UnknownFunctionException, UnparsableExpressionException{
		this(populationSize,mutationPropability,crossPropability, elitePercent, function);
		this.variables = variables;
		createRateFunction();
	}

	public int getPopulationSize() {
		return populationSize;
	}

	public void setPopulationSize(int populationSize) {
		this.populationSize = populationSize;
	}

	public double getMutationPropability() {
		return mutationPropability;
	}

	public void setMutationPropability(double mutationPropability) {
		this.mutationPropability = mutationPropability;
	}

	public double getCrossPropability() {
		return crossPropability;
	}

	public void setCrossPropability(double crossPropability) {
		this.crossPropability = crossPropability;
	}

	public String getFunction() {
		return function;
	}

	public void setFunction(String function) throws UnknownFunctionException, UnparsableExpressionException {
		this.function = function;
		createRateFunction();
	}

	public String[] getVariables() {
		return variables;
	}

	public void setVariables(String[] variables) throws UnknownFunctionException, UnparsableExpressionException {
		this.variables = variables;
		createRateFunction();
	}
	
	public ArrayList<Range> getRanges() {
		return ranges;
	}

	public void setRanges(ArrayList<Range> ranges) {
		this.ranges = ranges;
	}
	
	public void setGenotypeClass(Class<? extends Genotype> genotypeClass){
		this.genotypeClass = genotypeClass;
	}
	
	public void setSelection(Selection selection){
		this.selection = selection;
	}
	
	public Population getBestPopulation(){
		return bestPopulation;
	}
	
	public Population getLastPopulation(){
		return getPrevPopulation();
	}
	
	public Population getFirstPopulation() {
		return firstPopulation;
	}

	public void setFirstPopulation(Population firstPopulation) {
		this.firstPopulation = firstPopulation;
	}

	public void setNumberOfIterations(int numberOfIterations) {
		this.numberOfIterations = numberOfIterations;
	}

	public void setStop(boolean stop) {
		this.stop = stop;
	}

	private void createRateFunction() throws UnknownFunctionException, UnparsableExpressionException{
		calculation = new RateFunction(function, variables);
	}
	
	public void execute(){
		for (int iteration = 1; (iteration <= numberOfIterations) || stop; iteration++) {

			Population population = new Population(populationSize, iteration);
			population.setGenotypeClass(genotypeClass);
			
			if(iteration == 1){
				population.generateIndividualsWithGenesRanges(ranges);
				if(firstPopulation != null){
					population = firstPopulation;
				}else{
					firstPopulation = population;
				}
			} else{
				Population prevPopulation = getPrevPopulation();
				Population tmpPopulation = new Population(populationSize, 0);
				tmpPopulation.setGenotypeClass(genotypeClass);
				selection.setPopulation(prevPopulation);
				
				for (int i = 0; i < populationSize/2; i++) {

					Parents parents = selection.selectParents();
					
					Individual parent1 = parents.getFirstParent();
					Individual parent2 = parents.getSecondParent();
					
					ArrayList<Individual> childs = new ArrayList<Individual>();
					if(random.nextDouble() < crossPropability){
						childs = parent1.cross(parent2);
					} else{
						childs.add(parent1.copy());
						childs.add(parent2.copy());
					}
					for (Individual child : childs) {
						if(random.nextDouble() < mutationPropability){
							child.mutate();
						}
						RangeCheck check = new RangeCheck(ranges,child);
						if(!check.isInRange()){
							child.generateWithGenesRanges(ranges);
						}
						tmpPopulation.addIndividual(child);
					}
				}
				tmpPopulation.rate(calculation);
				sortPopulation(tmpPopulation);
				
				
				sortPopulation(prevPopulation);
				for(int i = 0; i < prevPopulation.getSize();i++){
					if(i < (elitePercent*populationSize)){
						population.addIndividual(prevPopulation.getIndividuals().get(i).copy());
					}else{
						population.addIndividual(tmpPopulation.getIndividuals().get(i).copy());
					}
				}
			}
			
			population.rate(calculation);
			sortPopulation(population);
			lastPopulation = population;
			
			if(bestPopulation == null || population.getRate() < bestPopulation.getRate()){
				bestPopulation = population;
			}
			
			setChanged();
			notifyObservers();
		}
	}
		
	private Population getPrevPopulation(){
		return lastPopulation;
	}
	
	private void sortPopulation(Population population){
		Collections.sort(population.getIndividuals(), new IndividualComparator());
	}
	

}
